#define _WIN32_WINNT   0x0500

#include "dx8_font.h"
#include "dx8graphics/dx8_graphics.h"
#include "graphics/graphics_util.h"
#include <D3D8.h>
#include <D3DX8.h>
#include <dxerr8.h>

#include "math/vec2.h"
#include "lang/lang.h"

#include "util/common_macros.h"		// macros
#include "util/log.h"		// macros
#include "util/mmgr.h"		// Needs to come in early
#include "util/timer.h"		// Needs to come in early


#include <cassert>

DX8Font::DX8Font( const std::string& Name ) :
	Font(Name),
	m_pTexture(NULL),
	m_hDC(0),
	m_hBMP(0),
	m_hFont(0),
	m_pBitmapBits(0),
	m_CacheGlyphC(0),
	m_pGlyphSet(0),
	m_pAltGlyphSet(0),
	m_hAltFont(0),
	m_DestSurf(0),
	m_ToCopyRect(false),
	m_TempX(0), m_TempY(0)
{
	m_Highlight = 0xFFFFFF00;
}

DX8Font::~DX8Font()
{
    DeInit();
	Font::~Font();
}

void	DX8Font::DeInit()
{
	if (m_hBMP)  DeleteObject( m_hBMP );	m_hBMP = 0;
    if (m_hDC)   DeleteDC( m_hDC ); m_hDC = 0;
    if (m_hFont) DeleteObject( m_hFont ); m_hFont = 0;
	if (m_hAltFont) DeleteObject( m_hAltFont ); m_hAltFont = 0;
	
	UTIL_SAFE_RELEASE(m_pTexture);
	UTIL_SAFE_DELETEA(m_pGlyphSet);
	UTIL_SAFE_DELETEA(m_pAltGlyphSet);	
	UTIL_SAFE_DELETEA(m_pGlyphSet);

	for (unsigned int i=0; i<m_pEmpty.size(); i++)
	{		
		delete m_pEmpty[i];
	}
	m_pEmpty.resize(0);
}

void	DX8Font::Resize(const std::string& _Face, int Width, int Height, int Weight, unsigned long CharSet)
{
	DeInit();
	LPDIRECT3DDEVICE8	pd3dDevice = GetDXGraphics()->GetDevice();

	m_Height = Height;				       
	m_Width  = Width;
	m_Weight = Weight;

	m_TexWidth = m_TexHeight = 1024;	
	// depending on the glyph size, we'll divide this into smaller sub-glyphs

	m_QuadSize = (int)(m_Height *1.8f);	// some glyphs are wider than they 
	int	GlyphWidth = m_TexWidth		/	m_QuadSize;
	int	GlyphHeight = m_TexHeight	/	m_QuadSize;
	for (int x=0; x<GlyphWidth; x++)
	for (int y=0; y<GlyphHeight; y++)
	{
		GlyphData* pGlyph = new GlyphData;
		pGlyph->TimeStamp	= -1;
		pGlyph->Glyph	= 0;
		pGlyph->PreOffset = 0;
		pGlyph->OffsetX = 0;
		pGlyph->Height	= pGlyph->Width = pGlyph->xp = pGlyph->yp = 0;	// doesn't matter
		pGlyph->TexX	= (x * m_QuadSize);
		pGlyph->TexY	= (y * m_QuadSize);
		pGlyph->TexW	= m_Width;		// should shrink to match exact size
		pGlyph->TexH	= m_Height;
		pGlyph->Pos		= math::Vec2(	float(pGlyph->TexX) / float(m_TexWidth), 
										float(pGlyph->TexY) / float(m_TexHeight));
		pGlyph->Size	= math::Vec2(	float(pGlyph->TexW) / float(m_TexWidth), 
										float(pGlyph->TexH) / float(m_TexHeight));
		m_pEmpty.push_back(pGlyph);
	}

	HRESULT  hRes = pd3dDevice->CreateTexture(  m_TexWidth, m_TexHeight, 0,
												0, //D3DUSAGE_DYNAMIC, 
												D3DFMT_A4R4G4B4,
												D3DPOOL_MANAGED, //D3DPOOL_DEFAULT, 
												&m_pTexture );
	if (FAILED(hRes))
	{
		_LOG(MSG_ERROR, "Unable to create Font texture for " << _Face << " : " << Dx8Graphics::GetDXError(hRes));
		return;
	}
	D3DLOCKED_RECT d3dlr;
	m_pTexture->LockRect( 0, &d3dlr, 0, 0 );
		memset(d3dlr.pBits, 0, m_TexHeight * d3dlr.Pitch);
	m_pTexture->UnlockRect(0);

	// Create a bitmap
    BITMAPINFO		bmi;
	ZeroMemory( &bmi.bmiHeader,  sizeof(BITMAPINFOHEADER) );
    bmi.bmiHeader.biSize        = sizeof(BITMAPINFOHEADER);
    bmi.bmiHeader.biWidth       =  (int)m_TexWidth;
    bmi.bmiHeader.biHeight      = -(int)m_TexHeight;
    bmi.bmiHeader.biPlanes      = 1;
    bmi.bmiHeader.biCompression = BI_RGB;
    bmi.bmiHeader.biBitCount    = 32;

    // Create a DC and a bitmap for the font
    m_hDC  = CreateCompatibleDC( NULL );
    m_hBMP = CreateDIBSection( m_hDC, &bmi, DIB_RGB_COLORS, (VOID**)&m_pBitmapBits, NULL, 0 );
    SetMapMode( m_hDC, MM_TEXT );

//	CharSet = DEFAULT_CHARSET;
    // Create a font.  By specifying ANTIALIASED_QUALITY, we might get an
    // antialiased font, but this is not guaranteed.
//    INT nHeight    = -MulDiv( m_Height, GetDeviceCaps(m_hDC, LOGPIXELSY), 72 );
    DWORD dwBold   = Weight;
    DWORD dwItalic = 0;			//(m_dwFontFlags&HG_FONT_ITALIC) ? TRUE    : FALSE;
    m_hFont		   = CreateFont( m_Height, m_Width, 0, 0, dwBold, dwItalic,
                          FALSE, FALSE, CharSet, OUT_DEFAULT_PRECIS,
                          CLIP_DEFAULT_PRECIS, 5, //ANTIALIASED_QUALITY,
						  VARIABLE_PITCH, _Face.c_str() );
    if( !m_hFont )
	{
		_LOG(MSG_ERROR, "Unable to create Font Object for " << _Face);
        return;
	}
	
	// get primary glyphset info
	SelectObject(m_hDC, m_hFont);
	unsigned long GlyphSize = GetFontUnicodeRanges( m_hDC, NULL );
	m_pGlyphSet = (new BYTE[GlyphSize]);
	((GLYPHSET *)m_pGlyphSet)->cbThis = GlyphSize;
	GetFontUnicodeRanges( m_hDC, (GLYPHSET *)m_pGlyphSet);

	SelectObject(m_hDC, m_hAltFont);
	 GlyphSize = GetFontUnicodeRanges( m_hDC, NULL );
	m_pAltGlyphSet = (new BYTE[GlyphSize]);
	((GLYPHSET *)m_pAltGlyphSet)->cbThis = GlyphSize;
	GetFontUnicodeRanges( m_hDC, (GLYPHSET *)m_pAltGlyphSet);
}

bool	DX8Font::GlyphSupported(unsigned short GlyphID, void* pGSet)
{
	GLYPHSET * pSet = (GLYPHSET *)pGSet;
	if (!pSet) return false;
	for (unsigned int i=0; i<pSet->cRanges; i++)
	{
		int From = pSet->ranges[i].wcLow;
		int To	 = pSet->ranges[i].wcLow+pSet->ranges[i].cGlyphs;
		if (From <= GlyphID && To >= GlyphID)
			return true;
	}
	// the damn font-mapper can't handle situations where
	// a glyph is in the base ASCII set, but not in the glyph.
	return GlyphID > 255;
	return true;
}

void	DX8Font::Render(	const std::wstring& Text, const math::Vec2& Pos, const math::Vec2& Size, unsigned int Flags, COLOR		Color, bool		Clip)
{

	if (Size.x <= 0.00001f) return ;	// gonna cause a crash!
	static	GlyphData*			GlyphA[MAX_CHAR];
	static	LineData			LineA[MAX_LINES];
	int							LineC=0, GlyphC=0;
	if (Text.empty()) return;

	Dx8Graphics*		pGraph = GetDXGraphics();
	int					ScreenW = pGraph->GetScreenWidth();
	int					ScreenH = pGraph->GetScreenHeight();

	pGraph->GetDevice()->SetTextureStageState( 0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR);
	pGraph->GetDevice()->SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR);
	pGraph->GetDevice()->SetTextureStageState( 0, D3DTSS_MIPFILTER, D3DTEXF_LINEAR);


	BuildGlyphData(Text, int(Size.x * ScreenW), int(Size.y* ScreenH), LineA, GlyphA, LineC, GlyphC, true);
	RenderGlyphData(Pos, Size, Color, LineA, GlyphA, LineC, GlyphC, Flags, true);

//	pGraph->GetDevice()->SetRenderState( D3DRS_ALPHATESTENABLE,     true );

/*	pGraph->GetDevice()->SetTexture(1, NULL);
	pGraph->GetDevice()->SetTextureStageState( 1, D3DTSS_COLOROP,   D3DTOP_DISABLE );
	pGraph->GetDevice()->SetTextureStageState( 1, D3DTSS_ALPHAOP,   D3DTOP_DISABLE ); 
*/
/*	static int LAST_FRAME = 0;
	LPDIRECT3DDEVICE8	pd3dDevice = pGraph->GetDevice();
	if (pGraph->GetFrame()!=LAST_FRAME)
	{
		LAST_FRAME = pGraph->GetFrame();
		Graphics::Vertex2D	VertA[6];
		pd3dDevice->SetTexture(0, m_pTexture);
		GraphicsUtil::FillQuad(math::Vec2(0,0), math::Vec2(1,1), math::Vec2(0.6,0.6), math::Vec2(0.99,0.99), 0xFF00FFFF, VertA);
		pGraph->DrawVertex2D(VertA, 6);
	}*/
}


void		DX8Font::SetTexture()
{
	Dx8Graphics*		pGraph = GetDXGraphics();
	LPDIRECT3DDEVICE8	pd3dDevice = pGraph->GetDevice();
	pd3dDevice->SetTexture(0, m_pTexture);
	pd3dDevice->SetTexture(1,NULL);
	pd3dDevice->SetTextureStageState( 1, D3DTSS_COLOROP,   D3DTOP_DISABLE );
	pd3dDevice->SetTextureStageState( 1, D3DTSS_ALPHAOP,   D3DTOP_DISABLE ); 
	pd3dDevice->SetRenderState( D3DRS_ALPHATESTENABLE,     FALSE );
}



void	DX8Font::FlushSprite()
{
	// do absolutely nothing
}

void	DX8Font::Flush()
{
	// do absolutely nothing
//	DeInit();
}

int		DX8Font::GetFontPixel(int x, int y)
{
	if (x < 0 || x >= m_TexWidth ||
		y < 0 || y >= m_TexHeight) return 0;

	int	Index = x + (y* m_TexWidth);
	return (m_pBitmapBits[Index]);
}

static void CacheGlyphCB(DX8Font::GlyphData* pGlyph, void* pData)
{
	DX8Font* pThis = (DX8Font*)pData;
	pThis->CacheGlyph(pGlyph);
}

static void UpdateGlyphCB(DX8Font::GlyphData* pGlyph, void* pData)
{
	DX8Font* pThis = (DX8Font*)pData;
	pThis->UpdateGlyph(pGlyph);

}

void DX8Font::UpdateGlyph(GlyphData* pGlyph)
{
	if (pGlyph->ToCache)
	{
//		int	x = 0;
//		int	 y = 0;
//		if (m_AddBorder) { x=1; y=1; };
		unsigned short str[] = L"x  ";
		char classbuff[5] = "";
		unsigned short buffer[5] = L"";
		SIZE size;
		HFONT	CurrentFont = m_hFont;

		str[0] = pGlyph->Glyph;

		bool	Supported = GlyphSupported(pGlyph->Glyph, m_pGlyphSet);
		if (!Supported)
		{
			Supported = GlyphSupported(pGlyph->Glyph, m_pAltGlyphSet);
			if (!Supported)
			{
				_LOG(MSG_ERROR, "Unable to find support for glyph " << pGlyph->Glyph << " in either font!");
			}
			if (CurrentFont!=m_hAltFont)
			{
				CurrentFont=m_hAltFont;
				SelectObject( m_hDC, CurrentFont );
			} else
			if (CurrentFont!=m_hFont)
			{
				CurrentFont=m_hFont;
				SelectObject( m_hDC, CurrentFont );
			} 
		} else
		{
			if (CurrentFont!=m_hFont)
			{
				CurrentFont=m_hFont;
				SelectObject( m_hDC, CurrentFont );
			} 
		}
	
		BOOL Res = GetTextExtentPoint32W( m_hDC, str, 1, &size );

		if( (m_TempX+size.cx+1) > m_TexWidth )
		{
			if (m_AddBorder)
			{
				m_TempX  = 1;
				m_TempY += m_QuadSize+3;
			} else
			{
				m_TempX = 0;
				m_TempY += m_QuadSize+2;
			}
		}

		// fill the area, in case we're about to get into trouble
		RECT	rect;
		if (m_AddBorder)
		{
			rect.left = m_TempX-1;
			rect.right = m_TempX+size.cx+3;
			rect.top  = m_TempY-1;
			rect.bottom = m_TempY+size.cy+2;
		} else
		{
			rect.left = m_TempX;
			rect.right = m_TempX+size.cx+2;
			rect.top  = m_TempY;
			rect.bottom = m_TempY+size.cy+1;
		}
		FillRect(m_hDC, &rect, (HBRUSH)GetStockObject(BLACK_BRUSH));

		if (m_AddBorder)
		{
			pGlyph->Height	= size.cy+2;
			pGlyph->Width	= size.cx+3;
	
			pGlyph->xp = m_TempX-1;
			pGlyph->yp = m_TempY-1;
		} else
		{
			pGlyph->Height	= size.cy;
			pGlyph->Width	= size.cx;
	
			pGlyph->xp = m_TempX;
			pGlyph->yp = m_TempY;
		}

		pGlyph->Size.x = float(pGlyph->Width) / float(m_TexWidth);
		pGlyph->Size.y = float(pGlyph->Height) / float(m_TexHeight);

		if (m_ToCopyRect)
		{
			BOOL Res = ExtTextOutW( m_hDC, m_TempX+0, m_TempY+0, ETO_OPAQUE, NULL, str, 1, NULL );
			if (Res == FALSE)
			{
				int j=0;
			}
		}

		if (m_AddBorder)
		{
			m_TempX += m_QuadSize + 4; //size.cx+4;
		} else
		{
			m_TempX += m_QuadSize; //size.cx+4;
		}
	}
}

void DX8Font::CacheGlyph(GlyphData* pGlyph)
{
	HFONT	CurrentFont = m_hFont;
	if (pGlyph->ToCache)
	{
		// copy this single glyph
		for (int y=0; y<pGlyph->Height; y++)
		{
			WORD* pLine = m_DestSurf + pGlyph->TexX + ( (pGlyph->TexY+y) * (m_TexWidth));
			if (m_AddBorder) pLine+= 1;	// starts 1 pixel further downstream
			for (int x=0; x<pGlyph->Width; x++)
			{
				int color = GetFontPixel(pGlyph->xp + x, pGlyph->yp+y);
				BYTE bAlpha = (BYTE)((color & 0xff) >> 4);
				if (bAlpha)
				{
					*pLine++ = (bAlpha << 12) | 0x0fff;
				}
				else
				{
					bool		Edge = false;
					if (m_AddBorder)
					{
						Edge |= ((GetFontPixel(pGlyph->xp + x - 1, pGlyph->yp+y))) > 0;
						Edge |= ((GetFontPixel(pGlyph->xp + x + 1, pGlyph->yp+y))) > 0;
						Edge |= ((GetFontPixel(pGlyph->xp + x, pGlyph->yp+y-1)) ) > 0;
						Edge |= ((GetFontPixel(pGlyph->xp + x, pGlyph->yp+y+1)) ) > 0;

						Edge |= ((GetFontPixel(pGlyph->xp + x - 1, pGlyph->yp+y-1))) > 0;
						Edge |= ((GetFontPixel(pGlyph->xp + x + 1, pGlyph->yp+y-1))) > 0;
						Edge |= ((GetFontPixel(pGlyph->xp + x - 1, pGlyph->yp+y-1)) ) > 0;
						Edge |= ((GetFontPixel(pGlyph->xp + x + 1, pGlyph->yp+y+1)) ) > 0;
					}

					if (!Edge)
					{
						*pLine++ = 0x0000;

					} else
					{
						*pLine++ = 0xF000;
					}
				}
			}
		}
		RECT	Dirty;
		Dirty.left = pGlyph->TexX;
		Dirty.top  = pGlyph->TexY;
		Dirty.right = pGlyph->TexX + pGlyph->Width;
		Dirty.bottom = pGlyph->TexY + pGlyph->Height;
		if (m_AddBorder) Dirty.right++;
		HRESULT hr = m_pTexture->AddDirtyRect(&Dirty);
		pGlyph->ToCache = false;
	}
}

void	DX8Font::CacheGlyphs(bool	ToCopyRect)
{
	if (m_CacheGlyphC <= 0) return;
	HFONT	CurrentFont = m_hFont;
	m_ToCopyRect = ToCopyRect;
	if (m_ToCopyRect)
	{
		SelectObject( m_hDC, m_hBMP );
		SelectObject( m_hDC, m_hFont );
		RECT	Rect = { 0, 0, m_Width, m_Height};


		// Set text properties
		SetTextColor( m_hDC, RGB(255,255,255) );
		SetBkColor(   m_hDC, 0x00000000 );
		SetTextAlign( m_hDC, TA_TOP );
		SetMapperFlags(m_hDC, 1);
	}

	// clear

    // Loop through all printable character and output them to the bitmap..
    // Meanwhile, keep track of the corresponding tex coords for each character.


	m_TempX = m_TempY = 0;
	if (m_AddBorder) { m_TempX=1; m_TempY=1; };
	m_GlyphMap.ForEach(UpdateGlyphCB, this);

	if (m_ToCopyRect)
	{
		// Lock the surface and write the alpha values for the set pixels
		D3DLOCKED_RECT d3dlr;
		HRESULT hres = m_pTexture->LockRect( 0, &d3dlr, 0, 0 );
		if (FAILED(hres))
		{
			_LOG(MSG_WARNING, "Unable to lock rect!");
		}
		assert(SUCCEEDED(hres));
		m_DestSurf= (WORD*)d3dlr.pBits;


		m_GlyphMap.ForEach(CacheGlyphCB, this);

		m_DestSurf = NULL; //invalidated soon
		// Done updating texture, so clean up used objects
		m_pTexture->UnlockRect(0);
	}
	// all cached!
	m_CacheGlyphC = 0;

}

DX8Font::GlyphData*	DX8Font::GetFreeGlyph(unsigned short ID)
{
	float	Stamp = TIMER().Time();
	const float		MIN_ELAPSED = 0.01f;
	if (!m_pEmpty.empty())
	{
		GlyphData* pGlyph = m_pEmpty[m_pEmpty.size()-1];
		m_pEmpty.resize(m_pEmpty.size()-1);
		pGlyph->Glyph = ID;
		pGlyph->TimeStamp = Stamp;
		pGlyph->OffsetX = 0;
		pGlyph->PreOffset = 0;
		pGlyph->ToCache = true;
		m_GlyphMap.AddElement(ID, pGlyph);
		m_CacheGlyphC++;
		return pGlyph;
	}

	float	Lowest = 10e10f;
	GlyphData*	pLowest = NULL;
	int	Size = m_GlyphMap.GetElementC();
	for ( int i=0; i<Size; i++)
	{
		GlyphData* pGlyph = m_GlyphMap.GetElementByIndex(i);
		float	Time = Stamp - pGlyph->TimeStamp;
		if (Time < Lowest && Time > MIN_ELAPSED)
		{
			pLowest = pGlyph;
			Lowest  = Time;
		}
	}

	if (pLowest)
	{
		m_GlyphMap.RemoveElement(pLowest->Glyph);
		pLowest->Glyph = ID;
		pLowest->TimeStamp = Stamp;
		pLowest->ToCache = true;
		m_GlyphMap.AddElement(ID, pLowest);
		m_CacheGlyphC++;
		return pLowest;
	}
	return NULL;
}

DX8Font::GlyphData* DX8Font::GetGlyphIndex(unsigned short Glyph)
{
	GlyphData*	pGlyph = m_GlyphMap.GetElement(Glyph);
	if (pGlyph) return pGlyph;
	// not in the current set.. can we recache?
	return GetFreeGlyph(Glyph);
}